﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace linkouqs.Gamekit {

    [RequireComponent(typeof(CapsuleCollider2D))]
    [RequireComponent(typeof(Rigidbody2D))]
    public class CharacterController2D : MonoBehaviour {

        [Tooltip("The Layers which represent gameobjects that the Character Controller can be grounded on.")]
        public LayerMask raycastLayers;
        [Tooltip("The distance down to check for ground.")]
        [Range(0.1f, 10f)]
        public float raycastDistance = 0.1f;
        [Range(3, 9)]
        public int raycastCount = 3;
        public bool checkIsCeilinged = true;
        public bool checkIsGrounded = true;
        public bool checkIsLefted = true;
        public bool checkIsRighted = true;

        Rigidbody2D m_Rigidbody2D;
        CapsuleCollider2D m_Capsule;
        Vector2 m_PreviousPosition;
        Vector2 m_CurrentPosition;
        Vector2 m_NextMovement;
        ContactFilter2D m_ContactFilter;
        List<Collider2D> m_CeilingColliders = new List<Collider2D>();
        List<Collider2D> m_GroundColliders = new List<Collider2D>();
        List<Collider2D> m_LeftColliders = new List<Collider2D>();
        List<Collider2D> m_RightColliders = new List<Collider2D>();

        public bool IsCeilinged { get; protected set; }
        public bool IsGrounded { get; protected set; }
        public bool IsLefted { get; protected set; }
        public bool IsRighted { get; protected set; }
        public Vector2 Velocity { get; protected set; }
        public Rigidbody2D Rigidbody2D { get { return m_Rigidbody2D; } }
        public List<Collider2D> CeilingColliders { get { return m_CeilingColliders; } }
        public List<Collider2D> GroundColliders { get { return m_GroundColliders; } }
        public List<Collider2D> LeftColliders { get { return m_LeftColliders; } }
        public List<Collider2D> RightColliders { get { return m_RightColliders; } }
        public ContactFilter2D ContactFilter { get { return m_ContactFilter; } }

        private void Awake() {
            m_Rigidbody2D = GetComponent<Rigidbody2D>();
            m_Capsule = GetComponent<CapsuleCollider2D>();

            m_CurrentPosition = m_Rigidbody2D.position;
            m_PreviousPosition = m_Rigidbody2D.position;

            m_ContactFilter.layerMask = raycastLayers;
            m_ContactFilter.useLayerMask = true;
            m_ContactFilter.useTriggers = false;

            Physics2D.queriesStartInColliders = false;
        }

        private void FixedUpdate() {
            m_PreviousPosition = m_Rigidbody2D.position;
            m_CurrentPosition = m_PreviousPosition + m_NextMovement;
            ComputeVelocity();

            m_Rigidbody2D.MovePosition(m_CurrentPosition);
            m_NextMovement = Vector2.zero;

            CheckCapsuleEndCollisions();
        }

        /// <summary>
        /// This moves a rigidbody and so should only be called from FixedUpdate or other Physics messages.
        /// </summary>
        /// <param name="movement">The amount moved in global coordinates relative to the rigidbody2D's position.</param>
        public void Move(Vector2 movement) {
            m_NextMovement += movement;
        }

        /// <summary>
        /// This moves the character without any implied velocity.
        /// </summary>
        /// <param name="position">The new position of the character in global space.</param>
        public void Teleport(Vector2 position) {
            Vector2 delta = position - m_CurrentPosition;
            m_PreviousPosition += delta;
            m_CurrentPosition = position;
            m_Rigidbody2D.MovePosition(position);
        }

        /// <summary>
        /// This updates the state of IsGrounded.  It is called automatically in FixedUpdate but can be called more frequently if higher accurracy is required.
        /// </summary>
        public void CheckCapsuleEndCollisions() {
            //IsCeilinged = false;
            //IsGrounded = false;
            //IsLefted = false;
            //IsRighted = false;

            if (checkIsCeilinged) {
                m_CeilingColliders = ComputeColliders(RaycastDirection.Up);
                IsCeilinged = m_CeilingColliders.Count > 0;
            }

            if(checkIsGrounded) {
                m_GroundColliders = ComputeColliders(RaycastDirection.Down);
                IsGrounded = m_GroundColliders.Count > 0;
            }

            if(checkIsLefted) {
                m_LeftColliders = ComputeColliders(RaycastDirection.Left);
                IsLefted = m_LeftColliders.Count > 0;
            }

            if(checkIsRighted) {
                m_RightColliders = ComputeColliders(RaycastDirection.Right);
                IsRighted = m_RightColliders.Count > 0;
            }
        }

        protected virtual void ComputeVelocity() {
            Velocity = (m_CurrentPosition - m_PreviousPosition) / Time.deltaTime;
        }

        List<Collider2D> ComputeColliders(RaycastDirection dir) {
            return m_Capsule.ComputeColliders(dir, raycastDistance, raycastLayers, raycastCount);
        }

        private void OnDrawGizmos() {
            var capsule = GetComponent<CapsuleCollider2D>();
            if(capsule) {
                capsule.DrawRaycast(raycastDistance, raycastCount);
            }
        }

    }

}


